package com.gitee.wsl.jvm.net.server

import com.gitee.wsl.jvm.net.server.HttpUriRooterManager.Companion.getMimeTypeForFile
import com.gitee.wsl.jvm.net.server.HttpUriRooterManager.Companion.getPathArray
import com.gitee.wsl.jvm.net.server.Response.Companion.newChunkedResponse
import com.gitee.wsl.prop.Prop
import com.gitee.wsl.prop.PropContainer
import com.gitee.wsl.prop.PropContainerImpl
import com.gitee.wsl.struct.router.CallHandler
import com.gitee.wsl.struct.router.CallHandlerFactory
import com.gitee.wsl.struct.router.FactoryCallHandler
import com.gitee.wsl.struct.router.Method
import com.gitee.wsl.struct.router.UriResource
import com.gitee.wsl.struct.router.UriRouterManager
import com.gitee.wsl.struct.router.buildSub
import com.gitee.wsl.struct.router.normalizeUri
import timber.log.Timber
import java.io.BufferedInputStream
import java.io.Closeable
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.net.ServerSocket
import java.net.Socket
import java.net.URL
import java.nio.file.Files
import java.util.Locale
import java.util.Properties

/*class HttpRouterServer {
    interface UriResponder {
        fun initContext(context: Context)
        fun initProps(session: IHTTPSession, urlParams: Map<String, String>)
        operator fun get(uriResource: UriResource, session: IHTTPSession): Response
        fun put(uriResource: UriResource, session: IHTTPSession): Response
        fun post(uriResource: UriResource, session: IHTTPSession): Response
        fun delete(uriResource: UriResource, session: IHTTPSession): Response
        fun other(method: Method, uriResource: UriResource, session: IHTTPSession): Response
        fun doHandler(session: IHTTPSession): Boolean
    }

    *//**
     * General nanolet to inherit from if you provide stream data, only chucked
     * responses will be generated.
     *//*
    abstract class DefaultStreamHandler : UriResponder {
        @JvmField
        protected var props: Props = PropsImpl()
        abstract val mimeType: String
        abstract val status: IStatus
        abstract fun getData(session: IHTTPSession): InputStream
        override fun initProps(session: IHTTPSession, urlParams: Map<String, String>) {}
        override fun get(uriResource: UriResource, session: IHTTPSession): Response {
            return other(Method.GET, uriResource, session)
        }

        override fun post(uriResource: UriResource, session: IHTTPSession): Response {
            return other(Method.POST, uriResource, session)
        }

        override fun put(uriResource: UriResource, session: IHTTPSession): Response {
            return other(Method.PUT, uriResource, session)
        }

        override fun delete(uriResource: UriResource, session: IHTTPSession): Response {
            return other(Method.DELETE, uriResource, session)
        }

        override fun other(
            method: Method,
            uriResource: UriResource,
            session: IHTTPSession
        ): Response {
            return newChunkedResponse(status, mimeType, getData(session))
        }

        override fun doHandler(session: IHTTPSession): Boolean {
            Timber.d("come in handler:" + this.javaClass.canonicalName)
            return false
        }

        protected fun initPropValue(head: Map<String, String>, vararg props: Prop<String>) {
            for (prop in props) {
                initPropValue(head, prop)
            }
        }

        protected fun initPropValue(head: Map<String, String>, prop: Prop<String>) {
            val value = head[prop.name()]
            if (value != null) prop[props] = prop.valueOf(value)
        }

        @JvmName("initPropValueBoolean")
        protected fun initPropValue(head: Map<String, String>, prop: Prop<Boolean>) {
            val value = head[prop.name()]
            if (value != null) prop[props] = prop.valueOf(value)
        }

        protected fun getPathArray(pathWithFileName: String): Array<String> {
            var removeQuerys = pathWithFileName
            if (pathWithFileName.contains("?"))
                removeQuerys = pathWithFileName.subSequence(0, pathWithFileName.indexOf("?")) as String
            return Companion.getPathArray(removeQuerys)
        }
    }

    *//**
     * General nanolet to inherit from if you provide text or html data, only
     * fixed size responses will be generated.
     *//*
    abstract class DefaultHandler : DefaultStreamHandler() {
        override var status: IStatus = Status.OK
            protected set

        abstract fun getText(session: IHTTPSession): String
        override fun initContext(context: Context) {}
        override fun get(uriResource: UriResource, session: IHTTPSession): Response {
            Timber.d("come in handler:%s", this.javaClass.canonicalName)
            var rest: String? = null
            rest = try {
                getText(session)
            } catch (e: Exception) {
                return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, mimeType, e.message!!)
            }
            return Response.newFixedLengthResponse(status, mimeType, rest!!)
        }

        override fun getData(session: IHTTPSession): InputStream {
            throw IllegalStateException("this method should not be called in a text based nanolet")
        }
    }

    *//**
     * General nanolet to print debug info's as a html page.
     *//*
    class GeneralHandler : DefaultHandler() {
        override fun getText(session: IHTTPSession): String {
            throw IllegalStateException("this method should not be called")
        }

        override val mimeType: String
            get() = "text/html"
        override var status: IStatus
            get() = Status.OK
            set(status) {
                super.status = status
            }

        override fun get(uriResource: UriResource, session: IHTTPSession): Response {
            val text = StringBuilder("<html><body>")
            text.append("<h1>Url.kt: ")
            text.append(session.uri)
            text.append("</h1><br>")
            val queryParams: Map<String, List<String?>> = session.parameters
            if (queryParams.isNotEmpty()) {
                for ((key, value1) in queryParams) {
                    val value = value1.toString()
                    text.append("<p>Param '")
                    text.append(key)
                    text.append("' = ")
                    text.append(value)
                    text.append("</p>")
                }
            } else {
                text.append("<p>no params in url</p><br>")
            }
            return Response.newFixedLengthResponse(status, mimeType, text.toString())
        }
    }

    *//**
     * General nanolet to print debug info's as a html page.
     *//*
    class StaticPageHandler : DefaultHandler() {
        override fun getText(session: IHTTPSession): String {
            throw IllegalStateException("this method should not be called")
        }

        override val mimeType: String
            get() {
                throw IllegalStateException("this method should not be called")
            }
        override var status: IStatus
            get() = Status.OK
            set(status) {
                super.status = status
            }

        override fun get(uriResource: UriResource, session: IHTTPSession): Response {
            val baseUri = uriResource.uri
            var realUri = normalizeUri(session.uri)
            for (index in 0 until Math.min(baseUri!!.length, realUri.length)) {
                if (baseUri[index] != realUri[index]) {
                    realUri = normalizeUri(realUri.substring(index))
                    break
                }
            }
            var fileOrdirectory = uriResource.initParameter(File::class.java)
            for (pathPart in getPathArray(realUri)) {
                fileOrdirectory = File(fileOrdirectory, pathPart)
            }

            fileOrdirectory?:return Error404UriHandler()[uriResource, session]

            if (fileOrdirectory.isDirectory) {
                fileOrdirectory = File(fileOrdirectory, "index.html")
                if (!fileOrdirectory.exists()) {
                    fileOrdirectory = File(fileOrdirectory.parentFile, "index.htm")
                }
            }
            return if (!fileOrdirectory.exists() || !fileOrdirectory.isFile) {
                Error404UriHandler()[uriResource, session]
            } else {
                try {
                    newChunkedResponse(
                        status,
                        getMimeTypeForFile(fileOrdirectory.name),
                        fileToInputStream(fileOrdirectory)
                    )
                } catch (ioe: IOException) {
                    Response.newFixedLengthResponse(
                        Status.REQUEST_TIMEOUT,
                        "text/plain",
                        ""
                    )
                }
            }
        }

        @Throws(IOException::class)
        protected fun fileToInputStream(fileOrdirectory: File): BufferedInputStream {
            return BufferedInputStream(Files.newInputStream(fileOrdirectory.toPath()))
        }
    }

    *//**
     * Handling error 404 - unrecognized urls
     *//*
    class Error404UriHandler : DefaultHandler() {
        override fun getText(session: IHTTPSession): String {
            return "<html><body><h3>Error 404: the requested page doesn't exist.</h3></body></html>"
        }

        override val mimeType: String
            get() = "text/html"
        override var status: IStatus
            get() = Status.NOT_FOUND
            set(status) {
                super.status = status
            }
    }

    *//**
     * Handling index
     *//*
    class IndexHandler : DefaultHandler() {
        override fun getText(session: IHTTPSession): String {
            return "<html><body><h2>Hello world!</h3></body></html>"
        }

        override val mimeType: String
            get() = "text/html"
        override var status: IStatus
            get() = Status.OK
            set(status) {
                super.status = status
            }
    }

    class NotImplementedHandler : DefaultHandler() {
        override fun getText(session: IHTTPSession): String {
            return "<html><body><h2>The uri is mapped in the router, but no handler is specified. <br> Status: Not implemented!</h3></body></html>"
        }

        override val mimeType: String
            get() = "text/html"
        override var status: IStatus
            get() = Status.OK
            set(status) {
                super.status = status
            }
    }

    class UriResource(
        uri: String?,
        val method: Method,
        private val handler: Class<*>?,
        vararg initParameter: Any
    ) : Comparable<UriResource> {
        var uri: String? = null
        private var uriPattern: Pattern? = null
        private var priority = 0
        private val initParameter: Array<out Any>
        private val uriParams: MutableList<String> = ArrayList()

        constructor(
            uri: String?,
            priority: Int,
            handler: Class<*>?,
            vararg initParameter: Any
        ) : this(uri, Method.GET, priority, handler, *initParameter)

        constructor(uri: String?, handler: Class<*>?, vararg initParameter: Any) : this(
            uri,
            Method.GET,
            handler,
            *initParameter
        )

        constructor(
            uri: String?,
            method: Method,
            priority: Int,
            handler: Class<*>?,
            vararg initParameter: Any
        ) : this(uri, method, handler, *initParameter) {
            this.priority = priority + uriParams.size * 1000
        }

        init {
            this.initParameter = initParameter
            if (uri != null) {
                this.uri = normalizeUri(uri)
                parse()
                uriPattern = createUriPattern()
            } else {
                uriPattern = null
                this.uri = null
            }
        }

        private fun parse() {}
        private fun createUriPattern(): Pattern {
            var patternUri = uri
            var matcher = PARAM_PATTERN.matcher(patternUri)
            var start = 0
            while (matcher.find(start)) {
                uriParams.add(patternUri!!.substring(matcher.start() + 1, matcher.end()))
                patternUri = StringBuilder(patternUri.substring(0, matcher.start())) //
                    .append(PARAM_MATCHER) //
                    .append(patternUri.substring(matcher.end())).toString()
                start = matcher.start() + PARAM_MATCHER.length
                matcher = PARAM_PATTERN.matcher(patternUri)
            }
            return Pattern.compile(patternUri)
        }

        fun process(urlParams: Map<String, String>, session: IHTTPSession): Response {
            var error = "General error!"
            if (handler != null) {
                try {
                    val `object` = handler.newInstance()
                    return if (`object` is UriResponder) {
                        val responder = `object`
                        responder.initContext(session.context)
                        responder.initProps(session, urlParams)
                        when (session.method) {
                            Method.GET -> responder[this, session]
                            Method.POST -> responder.post(this, session)
                            Method.PUT -> responder.put(this, session)
                            Method.DELETE -> responder.delete(this, session)
                            else -> responder.other(session.method, this, session)
                        }
                    } else {
                        Response.newFixedLengthResponse(
                            Status.OK, "text/plain",  //
                            StringBuilder("Return: ") //
                                .append(handler.canonicalName) //
                                .append(".toString() -> ") //
                                .append(`object`) //
                                .toString()
                        )
                    }
                } catch (e: Exception) {
                    error = "Error: " + e.javaClass.name + " : " + e.message
                    Timber.d(error, e)
                }
            }
            return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", error)
        }

        fun doHandler(params: Map<String, String>, session: IHTTPSession): Boolean {
            var error = "General error!"
            if (handler != null) {
                try {
                    val `object` = handler.newInstance()
                    if (`object` is UriResponder) {
                        val responder = `object`
                        responder.initContext(session.context)
                        responder.initProps(session, params)
                        return responder.doHandler(session)
                    }
                } catch (e: Exception) {
                    error = "Error: " + e.javaClass.name + " : " + e.message
                    Timber.d(error, e)
                }
            }
            return false
        }

        override fun toString(): String {
            return StringBuilder("UrlResource{uri='").append(uri ?: "/") //
                .append("', urlParts=").append(uriParams) //
                .append('}') //
                .toString()
        }



        fun match(url: String): Map<String, String> {
            val matcher = uriPattern!!.matcher(url)
            return if (matcher.matches()) {
                if (uriParams.size > 0) {
                    val result: MutableMap<String, String> = HashMap()
                    for (i in 1..matcher.groupCount()) {
                        result[uriParams[i - 1]] = matcher.group(i)
                    }
                    result
                } else {
                    EMPTY
                }
            } else EMPTY
        }

        override operator fun compareTo(that: UriResource): Int {
            return  if (method.ordinal < that.method.ordinal) {
                1
            } else if (priority > that.priority) {
                1
            } else if (priority < that.priority) {
                -1
            } else {
                0
            }
        }

        fun setPriority(priority: Int) {
            this.priority = priority
        }

        companion object {
            private val EMPTY = Collections.unmodifiableMap(HashMap<String, String>())
        }
    }

    interface IRRoutePrioritize {

        var notImplemented: Class<*>

        fun addRoute(url: String, priority: Int, handler: Class<*>, vararg initParameter: Any)

        fun addRoute(
            url: String,
            method: Method,
            priority: Int,
            handler: Class<*>,
            vararg initParameter: Any
        )

        fun removeRoute(url: String, method: Method)
        val prioritizedRoutes: Collection<UriResource>

    }

    abstract class BaseRoutePrioritize(val mappings: MutableCollection<UriResource>) : IRRoutePrioritize {

         override var notImplemented: Class<*> = NotImplementedHandler::class.java

        override fun addRoute(
            url: String,
            priority: Int,
            handler: Class<*>,
            vararg initParameter: Any
        ) {
            addRoute(url, Method.GET, priority, handler, *initParameter)
        }

        override fun addRoute(
            url: String,
            method: Method,
            priority: Int,
            handler: Class<*>,
            vararg initParameter: Any
        ) {
            if (handler != null) {
                    mappings.add(
                        UriResource(
                            url,
                            method,
                            priority + mappings.size,
                            handler,
                            *initParameter
                        )
                    )
            } else {
                    mappings.add(UriResource(url, method, priority + mappings.size, notImplemented))
            }

        }

        override fun removeRoute(url: String, method: Method) {
            val uriToDelete = normalizeUri(url)
            val iter = mappings.iterator()
            while (iter.hasNext()) {
                val uriResource = iter.next()
                if (uriToDelete == uriResource.uri && uriResource.method === method) {
                    iter.remove()
                    break
                }
            }
        }

        override val prioritizedRoutes: Collection<UriResource>
            get() = Collections.unmodifiableCollection(mappings)

    }

    class ProvidedPriorityRoutePrioritize : BaseRoutePrioritize(PriorityQueue()) {
        @SuppressLint("SuspiciousIndentation")
        override fun addRoute(
            url: String,
            priority: Int,
            handler: Class<*>,
            vararg initParameter: Any
        ) {
            val resource = UriResource(
                        url,
                        handler,
                        *initParameter
                    )

                resource.setPriority(priority)
                mappings.add(resource)
        }


    }

    class DefaultRoutePrioritize : BaseRoutePrioritize(PriorityQueue()) {

    }

    class InsertionOrderRoutePrioritize : BaseRoutePrioritize(ArrayList()) {

    }

    class UriRouter {
        private var error404Url: UriResource =  UriResource(null, 100, Error404UriHandler::class.java)
        private var routePrioritizer: IRRoutePrioritize

        init {
            routePrioritizer = DefaultRoutePrioritize()
        }

        *//**
         * Search in the mappings if the given url matches some of the rules If
         * there are more than one marches returns the rule with less parameters
         * e.g. mapping 1 = /user/:id mapping 2 = /user/help if the incoming uri
         * is www.example.com/user/help - mapping 2 is returned if the incoming
         * uri is www.example.com/user/3232 - mapping 1 is returned
         *
         * @param session
         * @return
         *//*
        fun process(session: IHTTPSession): Response {
            val work = normalizeUri(session.uri)
            val uriResource = matchUri(work, session.method)
            val params = uriResource.match(work)
            return uriResource.process(params, session)
        }

        private fun matchUri(workUri: String, method: Method): UriResource {
            var uriResource = error404Url
            var params: Map<String, String>? = null
            for (u in routePrioritizer.prioritizedRoutes) {
                if (u.method === method || u.method === Method.ALL) {
                    params = u.match(workUri)
                    if (params != null) {
                        uriResource = u
                        break
                    }
                }
            }
            return uriResource
        }

        fun addRoute(url: String, priority: Int, handler: Class<*>, vararg initParameter: Any) {
            addRoute(url, Method.GET, priority, handler, *initParameter)
        }

        fun addRoute(
            url: String,
            method: Method,
            priority: Int,
            handler: Class<*>,
            vararg initParameter: Any
        ) {
            routePrioritizer.addRoute(url, method, priority, handler, *initParameter)
        }

        fun removeRoute(url: String, method: Method = Method.GET) {
            routePrioritizer.removeRoute(url, method)
        }

        fun setNotFoundHandler(handler: Class<*>?) {
            error404Url = UriResource(null, 100, handler)
        }

        fun setNotImplemented(handler: Class<*>) {
            routePrioritizer.notImplemented = handler
        }

        fun setRoutePrioritizer(routePrioritizer: IRRoutePrioritize) {
            this.routePrioritizer = routePrioritizer
        }

        fun doHandler(session: IHTTPSession): Boolean {
            val work = normalizeUri(session.uri)
            val uriResource = matchUri(work, session.method)
            val params = uriResource.match(work)
            return uriResource.doHandler(params, session)
        }
    }

    private val router = UriRouter()

    *//**
     * default routings, they are over writable.
     *
     * <pre>
     * router.setNotFoundHandler(GeneralHandler.class);
    </pre> *
     *//*
    fun addMappings() {
        router.setNotImplemented(NotImplementedHandler::class.java)
        router.setNotFoundHandler(Error404UriHandler::class.java)
        router.addRoute("/", Int.MAX_VALUE / 2, IndexHandler::class.java)
        router.addRoute("/index.html", Int.MAX_VALUE / 2, IndexHandler::class.java)
    }

    fun addRoute(url: String, handler: Class<*>, vararg initParameter: Any) {
        router.addRoute(url, 100, handler, *initParameter)
    }

    fun addRoute(url: String, priority: Int, handler: Class<*>, vararg initParameter: Any) {
        router.addRoute(url, priority, handler, *initParameter)
    }

    fun addRoute(url: String, method: Method, handler: Class<*>, vararg initParameter: Any) {
        router.addRoute(url, method, 100, handler, *initParameter)
    }

    fun addRoute(
        url: String,
        method: Method,
        priority: Int,
        handler: Class<*>,
        vararg initParameter: Any
    ) {
        router.addRoute(url, method, priority, handler, *initParameter)
    }

    fun <T : UriResponder> setNotImplementedHandler(handler: Class<T>) {
        router.setNotImplemented(handler)
    }

    fun <T : UriResponder> setNotFoundHandler(handler: Class<T>) {
        router.setNotFoundHandler(handler)
    }

    @JvmOverloads
    fun removeRoute(url: String, method: Method = Method.ALL) {
        router.removeRoute(url, method)
    }

    fun setRoutePrioritizer(routePrioritizer: IRRoutePrioritize) {
        router.setRoutePrioritizer(routePrioritizer)
    }

    fun doHandler(session: IHTTPSession): Boolean {
        // Try to find match
        return router.doHandler(session)
    }

    fun service(session: IHTTPSession): Response {
        // Try to find match
        return router.process(session)
    }


    companion object {
        *//**
         * logger to log to.
         *//*
        private val LOG = Logger.getLogger(HttpRouterServer::class.java.name)

        fun getPathArray(uri: String): Array<String> {
            val array = uri.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val pathArray = ArrayList<String>()
            for (s in array) {
                if (s.isNotEmpty()) pathArray.add(s)
            }
            return pathArray.toArray(arrayOf())
        }

        *//**
         * Get MIME type from file name extension, if possible
         *
         * @param uri
         * the string representing a file
         * @return the connected mime/type
         *//*
        fun getMimeTypeForFile(uri: String): String {
            val dot = uri.lastIndexOf('.')
            var mime: String? = null
            if (dot >= 0) {
                mime = mimeTypes()[uri.substring(dot + 1)
                    .lowercase(Locale.getDefault())]
            }
            return mime ?: "application/octet-stream"
        }

        *//**
         * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
         *//*
        protected var MIME_TYPES = mutableMapOf<String, String>().apply {
            loadMimeTypes(this, "META-INF/default-default-mimetypes.properties")
            loadMimeTypes(this, "META-INF/default-mimetypes.properties")
            if (this.isEmpty()) {
                Timber.d("no mime types found in the classpath! please provide default-mimetypes.properties")
            }
        }
        fun mimeTypes(): Map<String, String> {
            return MIME_TYPES
        }

        private fun loadMimeTypes(result: MutableMap<String, String>?, resourceName: String) {
            try {
                val resources = HttpRouterServer::class.java.classLoader.getResources(resourceName)
                while (resources.hasMoreElements()) {
                    val url = resources.nextElement() as URL
                    val properties = Properties()
                    var stream: InputStream? = null
                    try {
                        stream = url.openStream()
                        properties.load(stream)
                    } catch (e: IOException) {
                        Timber.d("could not load mimetypes from $url", e)
                    } finally {
                        safeClose(stream)
                    }
                    result!!.putAll(properties as Map<String, String>)
                }
            } catch (e: IOException) {
                Timber.d("no mime types available at $resourceName")
            }
        }

        @SuppressLint("TimberArgCount")
        fun safeClose(closeable: Any?) {
            try {
                if (closeable != null) {
                    if (closeable is Closeable) {
                        closeable.close()
                    } else if (closeable is Socket) {
                        closeable.close()
                    } else if (closeable is ServerSocket) {
                        closeable.close()
                    } else {
                        throw IllegalArgumentException("Unknown object to close")
                    }
                }
            } catch (e: IOException) {
                Timber.d("Could not close", e)
            }
        }

        fun normalizeUri(value: String): String {
            var value = value
            if (value.startsWith("/")) {
                value = value.substring(1)
            }
            if (value.endsWith("/")) {
                value = value.substring(0, value.length - 1)
            }
            return value
        }

        val PARAM_PATTERN = Pattern.compile("(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))")
        const val PARAM_MATCHER = "([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=\\s]+)"
        fun createUriPattern(patternUri: String): String {
            var patternUri = patternUri
            var matcher = PARAM_PATTERN.matcher(patternUri)
            var start = 0
            while (matcher.find(start)) {
                patternUri = StringBuilder(patternUri.substring(0, matcher.start())) //
                    .append(PARAM_MATCHER) //
                    .append(patternUri.substring(matcher.end())).toString()
                start = matcher.start() + PARAM_MATCHER.length
                matcher = PARAM_PATTERN.matcher(patternUri)
            }
            return patternUri
        }
    }
}*/



interface HttpCallHandler:CallHandler<IHTTPSession>{
     var mimeType: String
     var status: IStatus
}

fun interface HttpCallHandlerFactory:CallHandlerFactory<IHTTPSession>

class HttpFactoryCallHandler(factory: HttpCallHandlerFactory): FactoryCallHandler<IHTTPSession>(factory)


fun sampleHttpCallHandler(responseBlock:(context: IHTTPSession, urlParams: Map<String, String>)-> Response): HttpCallHandler {
    return object : HttpCallHandler {
        override var mimeType: String = ""
        override var status: IStatus = Status.OK

        override fun call(
            context: IHTTPSession,
            resource: UriResource<IHTTPSession>,
            urlParams: Map<String, String>
        ) {
            context.response = responseBlock(context, urlParams)
        }
    }
}

fun sampleHttpCallHandlerFactory(responseBlock:(context: IHTTPSession, urlParams: Map<String, String>)-> Response): HttpCallHandlerFactory {
    return HttpCallHandlerFactory {
        object : HttpCallHandler {
            override var mimeType: String = ""
            override var status: IStatus = Status.OK

            override fun call(
                context: IHTTPSession,
                resource: UriResource<IHTTPSession>,
                urlParams: Map<String, String>
            ) {
                context.response = responseBlock(context, urlParams)
            }
        }
    }
}

interface HttpRouterScope{
    fun router(method: Method, path:String, priority:Int=100, handler:CallHandler<IHTTPSession>)

    fun router(method: Method, path:String, priority:Int=100, handler: HttpCallHandlerFactory){
        router(method, path, priority, HttpFactoryCallHandler(handler))
    }

    fun get( path:String, handler: HttpCallHandler){
        router(Method.GET,path,100, handler)
    }

    fun get( path:String, priority:Int=100, handler: HttpCallHandler){
        router(Method.GET,path,priority, handler)
    }

    fun get( path:String,  handler: HttpCallHandlerFactory){
        router(Method.GET,path,100, handler)
    }
    fun get( path:String, priority:Int=100, handler: HttpCallHandlerFactory){
        router(Method.GET,path,priority, handler)
    }

    fun put( path:String, handler: HttpCallHandler){
        router(Method.PUT,path,100, handler)
    }

    fun put( path:String, handler: HttpCallHandlerFactory){
        router(Method.PUT,path,100, handler)
    }

    fun post( path:String, handler: HttpCallHandler){
        router(Method.POST,path,100, handler)
    }

    fun post( path:String, handler: HttpCallHandlerFactory){
        router(Method.POST,path,100, handler)
    }

    fun delete( path:String, handler: HttpCallHandler){
        router(Method.DELETE,path,100, handler)
    }

    fun delete( path:String, handler: HttpCallHandlerFactory){
        router(Method.DELETE,path,100, handler)
    }

    fun patch( path:String, handler: HttpCallHandler){
        router(Method.PATCH,path,100, handler)
    }

    fun patch( path:String, handler: HttpCallHandlerFactory){
        router(Method.PATCH,path,100, handler)
    }

    fun on(url:String,factory: HttpRouterScope.()->Unit)
}

interface HttpUriRouterScope: HttpRouterScope {

    fun defaultHandler(handler: CallHandler<IHTTPSession>)

    fun config(configRouter: UriRouterManager<IHTTPSession>.() -> Unit)

}

class HttpRouterScopeImp(val baseDir:String,val rooterManager: HttpUriRooterManager):
    HttpRouterScope {

    override fun router(
        method: Method,
        path: String,
        priority: Int,
        handler: CallHandler<IHTTPSession>
    ) {
        rooterManager.addRoute(buildSub(baseDir,path),handler,method,priority)
    }

    override fun on(url: String, factory: HttpRouterScope.() -> Unit) {
        val sub = HttpRouterScopeImp(buildSub(baseDir,url),rooterManager)
        factory(sub)
    }

}

fun sampleHttpRouter(baseDir: String="/",routerConfig: HttpUriRouterScope.()->Unit): HttpUriRooterManager {
    val router = HttpUriRooterManager()
    val parent= object: HttpUriRouterScope {
        override fun router(
            method: Method,
            path: String,
            priority: Int,
            handler: CallHandler<IHTTPSession>
        ) {
            router.addRoute(buildSub(baseDir,path),handler,method,priority)
        }

        override fun on(url: String, factory: HttpRouterScope.() -> Unit) {
            val baseRoute = HttpRouterScopeImp(buildSub(baseDir,url),router)
            factory(baseRoute)
        }

        override fun defaultHandler(handler: CallHandler<IHTTPSession>) {
            router.setNotImplemented(handler)
        }

        override fun config(configRouter: UriRouterManager<IHTTPSession>.() -> Unit) {
            configRouter(router)
        }
    }
    routerConfig(parent)
    return router
}

class HttpUriRooterManager: UriRouterManager<IHTTPSession>() {
    //private var error404Url: HttpRouterServer.UriResource = HttpRouterServer.UriResource(null, 100, HttpRouterServer.Error404UriHandler::class.java)


    /**
     * default routings, they are over writable.
     *
     * <pre>
     * router.setNotFoundHandler(GeneralHandler.class);
    </pre> *
     */
    fun addMappings() {
        //router.setNotImplemented(HttpRouterServer.NotImplementedHandler::class.java)
        //router.setNotFoundHandler(HttpRouterServer.Error404UriHandler::class.java)
        router.addRoute("/", priority = Int.MAX_VALUE / 2, handler = IndexHandler())
        router.addRoute("/index.html", priority = Int.MAX_VALUE / 2, handler = IndexHandler())
    }


    companion object{

        fun getPathArray(uri: String): Array<String> {
            val array = uri.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            val pathArray = ArrayList<String>()
            for (s in array) {
                if (s.isNotEmpty()) pathArray.add(s)
            }
            return pathArray.toArray(arrayOf())
        }

        /**
        * Get MIME type from file name extension, if possible
        *
        * @param uri
        * the string representing a file
        * @return the connected mime/type
        */
        fun getMimeTypeForFile(uri: String): String {
            val dot = uri.lastIndexOf('.')
            var mime: String? = null
            if (dot >= 0) {
                mime = mimeTypes()[uri.substring(dot + 1)
                    .lowercase(Locale.getDefault())]
            }
            return mime ?: "application/octet-stream"
        }

        /**
        * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
        */
        protected var MIME_TYPES = mutableMapOf<String, String>().apply {
                loadMimeTypes(this, "META-INF/default-default-mimetypes.properties")
                loadMimeTypes(this, "META-INF/default-mimetypes.properties")
                if (this.isEmpty()) {
                    Timber.d("no mime types found in the classpath! please provide default-mimetypes.properties")
                }
            }
        fun mimeTypes(): Map<String, String> {
            return MIME_TYPES
        }

        private fun loadMimeTypes(result: MutableMap<String, String>?, resourceName: String) {
            try {
                val resources = HttpUriRooterManager::class.java.classLoader?.getResources(resourceName)?:return
                while (resources.hasMoreElements()) {
                    val url = resources.nextElement() as URL
                    val properties = Properties()
                    var stream: InputStream? = null
                    try {
                        stream = url.openStream()
                        properties.load(stream)
                    } catch (e: IOException) {
                        Timber.d("could not load mimetypes from $url", e)
                    } finally {
                        safeClose(stream)
                    }
                    result!!.putAll(properties as Map<String, String>)
                }
            } catch (e: IOException) {
                Timber.d("no mime types available at $resourceName")
            }
        }

        fun safeClose(closeable: Any?) {
            try {
                if (closeable != null) {
                    if (closeable is Closeable) {
                        closeable.close()
                    } else if (closeable is Socket) {
                        closeable.close()
                    } else if (closeable is ServerSocket) {
                        closeable.close()
                    } else {
                        throw IllegalArgumentException("Unknown object to close")
                    }
                }
            } catch (e: IOException) {
                Timber.d("Could not close", e)
            }
        }
    }
}


abstract class AbstractHttpCallHandler: HttpCallHandler {

    override var mimeType: String = "text/html"

    override var status: IStatus = Status.OK

    val props: PropContainer = PropContainerImpl()

    open fun initProps(context: IHTTPSession,
                       urlParams: Map<String, String>){}

    fun initPropValue(head: Map<String, String>, vararg propArray: Prop<String>) {
        for (prop in propArray) {
            initPropValue(head, prop)
        }
    }

    fun initPropValue(head: Map<String, String>, prop: Prop<String>) {
        val value = head[prop.name()]
        if (value != null) prop[props] = prop.valueOf(value)
    }

    @JvmName("initPropValueBoolean")
    fun initPropValue(head: Map<String, String>, prop: Prop<Boolean>) {
        val value = head[prop.name()]
        if (value != null) prop[props] = prop.valueOf(value)
    }

}


abstract class DefaultStreamHandler : AbstractHttpCallHandler() {

    override fun call(
        context: IHTTPSession,
        resource: UriResource<IHTTPSession>,
        urlParams: Map<String, String>
    ) {
       initProps(context, urlParams)
       context.response = newChunkedResponse(status, mimeType, getData(context))
    }

    abstract fun getData(session: IHTTPSession): InputStream

}

abstract class DefaultHandler : AbstractHttpCallHandler() {

    abstract fun getText(session: IHTTPSession): String

    override fun call(
        context: IHTTPSession,
        resource: UriResource<IHTTPSession>,
        urlParams: Map<String, String>
    ) {
        initProps(context, urlParams)
        val rest: String? = try {
            getText(context)
        } catch (e: Exception) {
            context.response=
                Response.newFixedLengthResponse(Status.INTERNAL_ERROR, mimeType, e.message!!)
            return
        }
        context.response = Response.newFixedLengthResponse(status, mimeType, rest!!)
    }

}

/**
 * General nanolet to print debug info's as a html page.
 */
class GeneralHandler : HttpCallHandler {

    override var status: IStatus = Status.OK

    override var mimeType: String="text/html"
    override fun call(
        context: IHTTPSession,
        resource: UriResource<IHTTPSession>,
        urlParams: Map<String, String>
    ) {
        val text = StringBuilder("<html><body>")
        text.append("<h1>Url.kt: ")
        text.append(context.uri)
        text.append("</h1><br>")
        val queryParams: Map<String, List<String?>> = context.parameters
        if (queryParams.isNotEmpty()) {
            for ((key, value1) in queryParams) {
                val value = value1.toString()
                text.append("<p>Param '")
                text.append(key)
                text.append("' = ")
                text.append(value)
                text.append("</p>")
            }
        } else {
            text.append("<p>no params in url</p><br>")
        }
        context.response = Response.newFixedLengthResponse(status, mimeType, text.toString())
    }
}

/**
 * General nanolet to print debug info's as a html page.
 */
open class StaticPageHandler(val basePath:File) : HttpCallHandler {

    override var status: IStatus = Status.OK

    override var mimeType: String="text/plain"

    override fun call(
        session: IHTTPSession,
        uriResource: UriResource<IHTTPSession>,
        urlParams: Map<String, String>
    ) {
        val baseUri = uriResource.uri
        var realUri = normalizeUri(session.uri)
        for (index in 0 until Math.min(baseUri.length, realUri.length)) {
            if (baseUri[index] != realUri[index]) {
                realUri = normalizeUri(realUri.substring(index))
                break
            }
        }
        var fileOrdirectory = basePath
        for (pathPart in getPathArray(realUri)) {
            fileOrdirectory = File(fileOrdirectory, pathPart)
        }

        if (fileOrdirectory.isDirectory) {
            fileOrdirectory = File(fileOrdirectory, "index.html")
            if (!fileOrdirectory.exists()) {
                fileOrdirectory = File(fileOrdirectory.parentFile, "index.htm")
            }
        }

         if (!fileOrdirectory.exists() || !fileOrdirectory.isFile) {
            Error404UriHandler.Instance.call( session,uriResource, urlParams)
        } else {
            try {
                session.response=newChunkedResponse(
                    status,
                    getMimeTypeForFile(fileOrdirectory.name),
                    fileToInputStream(fileOrdirectory)
                )
            } catch (ioe: IOException) {
                session.response= Response.newFixedLengthResponse(
                    Status.REQUEST_TIMEOUT,
                    "text/plain",
                    ""
                )
            }
        }
    }


    @Throws(IOException::class)
    protected fun fileToInputStream(fileOrdirectory: File): BufferedInputStream {
        return BufferedInputStream(Files.newInputStream(fileOrdirectory.toPath()))
    }

    companion object{
        fun Factory(basePath:File) = CallHandlerFactory{ StaticPageHandler(basePath) }
    }
}

/**
 * Handling error 404 - unrecognized urls
 */
class Error404UriHandler : DefaultHandler() {
    override fun getText(session: IHTTPSession): String {
        return "<html><body><h3>Error 404: the requested page doesn't exist.</h3></body></html>"
    }

    override var status: IStatus = Status.NOT_FOUND

    companion object{
        val Instance= Error404UriHandler()
    }
}

/**
 * Handling index
 */
class IndexHandler : DefaultHandler() {
    override fun getText(session: IHTTPSession): String {
        return "<html><body><h2>Hello world!</h3></body></html>"
    }

}

class NotImplementedHandler : DefaultHandler() {
    override fun getText(session: IHTTPSession): String {
        return "<html><body><h2>The uri is mapped in the router, but no handler is specified. <br> Status: Not implemented!</h3></body></html>"
    }

    companion object{
        val Instance= NotImplementedHandler()
    }

}